#include "BBN.h"
#include <cmath>
#include <string>
#include <sstream>
#include <stdexcept>

namespace BBN
{





static unsigned int numjoysticks = 0;
static SDL_Joystick** joysticks = NULL;



// Private hidden data
class InputData
{
public:
    Uint8* keystate;
    SDL_Event event;
    
    InputData()
    {
        keystate = SDL_GetKeyboardState(NULL);
    }
};


Input::Input()
    : gamepads(*this), gamepad1(*this, 1), gamepad2(*this, 2), gamepad3(*this, 3), gamepad4(*this, 4)
{
    data = new InputData;
    if(joysticks == NULL)
        reload_gamepads();
}

Input::~Input()
{
    delete (InputData*)data;
}

void Input::reload_gamepads()
{
    numjoysticks = SDL_NumJoysticks();

    if(numjoysticks > 0)
    {
        for(unsigned int i = 0; i < numjoysticks; i++ )
        {
            SDL_JoystickClose(SDL_JoystickOpen(i));
        }
    }

    if(SDL_WasInit(SDL_INIT_JOYSTICK) & SDL_INIT_JOYSTICK)
        SDL_QuitSubSystem(SDL_INIT_JOYSTICK);
    delete[] joysticks;
    joysticks = NULL;

    // Init joysticks
    SDL_InitSubSystem(SDL_INIT_JOYSTICK);

    numjoysticks = SDL_NumJoysticks();
    if(numjoysticks > 0)
    {
        {
            stringstream s;
            s << "Opening joysticks: " << numjoysticks << " found...";
            print_line(s.str());
        }

        joysticks = new SDL_Joystick*[numjoysticks];

        for(unsigned int i = 0; i < numjoysticks; i++ )
        {
            joysticks[i] = SDL_JoystickOpen(i);
        }

    }
}

unsigned int Input::get_num_gamepads() const
{
    return (numjoysticks > 4? 4 : numjoysticks);
}

bool Input::next_event()
{
    SDL_Event& event = ((InputData*)data)->event;
    bool result = SDL_PollEvent(&event);
    
    switch(event.type)
    {
    case SDL_MOUSEBUTTONDOWN:
    case SDL_MOUSEBUTTONUP:
        _mx = event.button.x;
        _my = event.button.y;
        break;
    case SDL_MOUSEMOTION:
        _mx = event.motion.x;
        _my = event.motion.y;
        break;
    case SDL_JOYDEVICEADDED:
        print_line("SDL_JOYDEVICEADDED");
        break;
    case SDL_JOYDEVICEREMOVED:
        print_line("SDL_JOYDEVICEREMOVED");
        break;
    case SDL_JOYAXISMOTION:
        Gamepad& g = get_event_gamepad();
        
        if(event.jaxis.axis == 0)
        {
            g.joystick1.last_horizontal_state = g.joystick1.event_horizontal_state;
            g.joystick1.event_horizontal_state = event.jaxis.value;
        }
        else if(event.jaxis.axis == 1)
        {
            g.joystick1.last_vertical_state = g.joystick1.event_vertical_state;
            g.joystick1.event_vertical_state = event.jaxis.value;
        }
        else if(event.jaxis.axis == 2)
        {
            g.joystick2.last_horizontal_state = g.joystick2.event_horizontal_state;
            g.joystick2.event_horizontal_state = event.jaxis.value;
        }
        else if(event.jaxis.axis == 3)
        {
            g.joystick2.last_vertical_state = g.joystick2.event_vertical_state;
            g.joystick2.event_vertical_state = event.jaxis.value;
        }
        else if(event.jaxis.axis == 4)
        {
            g.joystick3.last_horizontal_state = g.joystick3.event_horizontal_state;
            g.joystick3.event_horizontal_state = event.jaxis.value;
        }
        else if(event.jaxis.axis == 5)
        {
            g.joystick3.last_vertical_state = g.joystick3.event_vertical_state;
            g.joystick3.event_vertical_state = event.jaxis.value;
        }
        break;
    }
    
    return result;
}

bool Input::is_quit() const
{
    SDL_Event& event = ((InputData*)data)->event;
    return (event.type == SDL_QUIT);
}

bool Input::is_key_event() const
{
    SDL_Event& event = ((InputData*)data)->event;
    return (event.type == SDL_KEYDOWN || event.type == SDL_KEYUP);
}

bool Input::is_mouse_event() const
{
    SDL_Event& event = ((InputData*)data)->event;
    return (event.type == SDL_MOUSEBUTTONDOWN || event.type == SDL_MOUSEBUTTONUP || event.type == SDL_MOUSEMOTION);
}

bool Input::is_gamepad_event() const
{
    SDL_Event& event = ((InputData*)data)->event;
    return (event.type == SDL_JOYAXISMOTION || event.type == SDL_JOYBALLMOTION
             || event.type == SDL_JOYBUTTONDOWN || event.type == SDL_JOYBUTTONUP 
             || event.type == SDL_JOYHATMOTION);
}

Gamepad& Input::get_event_gamepad()
{
    if(is_gamepad_event())
    {
        SDL_Event& event = ((InputData*)data)->event;
        
        unsigned int which = 0;
        switch(event.type)
        {
        case SDL_JOYAXISMOTION:
            which = event.jaxis.which;
            break;
        case SDL_JOYBALLMOTION:
            which = event.jball.which;
            break;
        case SDL_JOYBUTTONDOWN:
            which = event.jbutton.which;
            break;
        case SDL_JOYBUTTONUP:
            which = event.jbutton.which;
            break;
        case SDL_JOYHATMOTION:
            which = event.jhat.which;
            break;
        }
        
        switch(which)
        {
        case 0:
            return gamepad1;
        case 1:
            return gamepad2;
        case 2:
            return gamepad3;
        case 3:
            return gamepad4;
        }
        
        print_line("Input::get_event_gamepad() called for unhandled gamepad event");
        return gamepad1;
    }
    else
        throw logic_error("Input::get_event_gamepad() called for a non-gamepad event");
}

bool Input::key_pressed(SDL_Scancode key)
{
    SDL_Event& event = ((InputData*)data)->event;
    return (event.type == SDL_KEYDOWN && event.key.keysym.scancode == key);
}

bool Input::key_released(SDL_Scancode key)
{
    SDL_Event& event = ((InputData*)data)->event;
    return (event.type == SDL_KEYUP && event.key.keysym.scancode == key);
}

bool Input::key_held(SDL_Scancode key)
{
    Uint8* keystate = ((InputData*)data)->keystate;
    return keystate[key];
}

bool Input::mouse_pressed(int button)
{
    SDL_Event& event = ((InputData*)data)->event;
    return (event.type == SDL_MOUSEBUTTONDOWN && event.button.button == button);
}

bool Input::mouse_released(int button)
{
    SDL_Event& event = ((InputData*)data)->event;
    return (event.type == SDL_MOUSEBUTTONUP && event.button.button == button);
}

bool Input::mouse_held(int button)
{
    return (SDL_GetMouseState(NULL, NULL) & button);
}

void Input::update_mouse()
{
    SDL_GetMouseState(&_mx, &_my);
}

int Input::mouse_x() const
{
    return _mx;
}

int Input::mouse_y() const
{
    return _my;
}



Input::GamepadSet::GamepadSet(Input& input)
    : input(input)
{}

Gamepad& Input::GamepadSet::operator[](int index)
{
    switch(index)
    {
        case 0:
            return input.gamepad1;
        case 1:
            return input.gamepad2;
        case 2:
            return input.gamepad3;
        case 3:
            return input.gamepad4;
    }
    throw logic_error("GamepadSet::operator[] got an invalid index.");
}

Gamepad const& Input::GamepadSet::operator[](int index) const
{
    switch(index)
    {
        case 0:
            return input.gamepad1;
        case 1:
            return input.gamepad2;
        case 2:
            return input.gamepad3;
        case 3:
            return input.gamepad4;
    }
    throw logic_error("GamepadSet::operator[] got an invalid index.");
}



Gamepad::Gamepad(Input& input, unsigned int id)
    : input(input), id(id), joystick1(*this, 1, 1, 2), joystick2(*this, 2, 3, 4), joystick3(*this, 3, 5, 6), button1(*this, 1), button2(*this, 2), button3(*this, 3), button4(*this, 4)
{}


#define JOY_AXIS_MAX 32768.0f
#define JOY_AXIS_DEFAULT_DEAD_ZONE 8000

Gamepad::Joystick::Joystick(Gamepad& gamepad, unsigned int id, unsigned int horizontal_axis_id, unsigned int vertical_axis_id)
    : gamepad(gamepad), id(id), horizontal_axis_id(horizontal_axis_id), vertical_axis_id(vertical_axis_id), last_horizontal_state(0), last_vertical_state(0), event_horizontal_state(0), event_vertical_state(0), dead_zone(JOY_AXIS_DEFAULT_DEAD_ZONE)
{}

bool Gamepad::Joystick::pressed_up() const
{
    if(gamepad.id == 0 || gamepad.id-1 >= numjoysticks)
        return false;

    SDL_Event& event = ((InputData*)gamepad.input.data)->event;
    if(event.type == SDL_JOYAXISMOTION)
    {
        if(event.jaxis.which != int(gamepad.id-1) || event.jaxis.axis != vertical_axis_id-1)
            return false;
        
        return (last_vertical_state >= -dead_zone && event_vertical_state < -dead_zone);
    }
    return false;
}

bool Gamepad::Joystick::released_up() const
{
    if(gamepad.id == 0 || gamepad.id-1 >= numjoysticks)
        return false;

    SDL_Event& event = ((InputData*)gamepad.input.data)->event;
    if(event.type == SDL_JOYAXISMOTION)
    {
        if(event.jaxis.which != int(gamepad.id-1) || event.jaxis.axis != vertical_axis_id-1)
            return false;
        
        return (last_vertical_state < -dead_zone && event_vertical_state >= -dead_zone);
    }
    return false;
}

float Gamepad::Joystick::value_up() const
{
    if(gamepad.id == 0 || gamepad.id-1 >= numjoysticks)
        return 0.0f;

    Sint16 value = SDL_JoystickGetAxis(joysticks[gamepad.id-1], vertical_axis_id-1);
    if(value > -dead_zone)
        return 0.0f;
    return (-value-dead_zone)/(JOY_AXIS_MAX-dead_zone);
}

bool Gamepad::Joystick::pressed_down() const
{
    if(gamepad.id == 0 || gamepad.id-1 >= numjoysticks)
        return false;

    SDL_Event& event = ((InputData*)gamepad.input.data)->event;
    if(event.type == SDL_JOYAXISMOTION)
    {
        if(event.jaxis.which != int(gamepad.id-1) || event.jaxis.axis != vertical_axis_id-1)
            return false;
        
        return (last_vertical_state <= dead_zone && event_vertical_state > dead_zone);
    }
    return false;
}

bool Gamepad::Joystick::released_down() const
{
    if(gamepad.id == 0 || gamepad.id-1 >= numjoysticks)
        return false;

    SDL_Event& event = ((InputData*)gamepad.input.data)->event;
    if(event.type == SDL_JOYAXISMOTION)
    {
        if(event.jaxis.which != int(gamepad.id-1) || event.jaxis.axis != vertical_axis_id-1)
            return false;
        
        return (last_vertical_state > dead_zone && event_vertical_state <= dead_zone);
    }
    return false;
}

float Gamepad::Joystick::value_down() const
{
    if(gamepad.id == 0 || gamepad.id-1 >= numjoysticks)
        return 0.0f;

    Sint16 value = SDL_JoystickGetAxis(joysticks[gamepad.id-1], vertical_axis_id-1);
    if(value < dead_zone)
        return 0.0f;
    return (value-dead_zone)/(JOY_AXIS_MAX-dead_zone);
}

bool Gamepad::Joystick::pressed_left() const
{
    if(gamepad.id == 0 || gamepad.id-1 >= numjoysticks)
        return false;

    SDL_Event& event = ((InputData*)gamepad.input.data)->event;
    if(event.type == SDL_JOYAXISMOTION)
    {
        if(event.jaxis.which != int(gamepad.id-1) || event.jaxis.axis != horizontal_axis_id-1)
            return false;
        
        return (last_horizontal_state >= -dead_zone && event_horizontal_state < -dead_zone);
    }
    return false;
}

bool Gamepad::Joystick::released_left() const
{
    if(gamepad.id == 0 || gamepad.id-1 >= numjoysticks)
        return false;

    SDL_Event& event = ((InputData*)gamepad.input.data)->event;
    if(event.type == SDL_JOYAXISMOTION)
    {
        if(event.jaxis.which != int(gamepad.id-1) || event.jaxis.axis != horizontal_axis_id-1)
            return false;
        
        return (last_horizontal_state < -dead_zone && event_horizontal_state >= -dead_zone);
    }
    return false;
}

float Gamepad::Joystick::value_left() const
{
    if(gamepad.id == 0 || gamepad.id-1 >= numjoysticks)
        return 0.0f;
    
    Sint16 value = SDL_JoystickGetAxis(joysticks[gamepad.id-1], horizontal_axis_id-1);
    if(value > -dead_zone)
        return 0.0f;
    return (-value-dead_zone)/(JOY_AXIS_MAX-dead_zone);
}

bool Gamepad::Joystick::pressed_right() const
{
    if(gamepad.id == 0 || gamepad.id-1 >= numjoysticks)
        return false;

    SDL_Event& event = ((InputData*)gamepad.input.data)->event;
    if(event.type == SDL_JOYAXISMOTION)
    {
        if(event.jaxis.which != int(gamepad.id-1) || event.jaxis.axis != horizontal_axis_id-1)
            return false;
        
        return (last_horizontal_state <= dead_zone && event_horizontal_state > dead_zone);
    }
    return false;
}

bool Gamepad::Joystick::released_right() const
{
    if(gamepad.id == 0 || gamepad.id-1 >= numjoysticks)
        return false;

    SDL_Event& event = ((InputData*)gamepad.input.data)->event;
    if(event.type == SDL_JOYAXISMOTION)
    {
        if(event.jaxis.which != int(gamepad.id-1) || event.jaxis.axis != horizontal_axis_id-1)
            return false;
        
        return (last_horizontal_state > dead_zone && event_horizontal_state <= dead_zone);
    }
    return false;
}

float Gamepad::Joystick::value_right() const
{
    if(gamepad.id == 0 || gamepad.id-1 >= numjoysticks)
        return 0.0f;

    Sint16 value = SDL_JoystickGetAxis(joysticks[gamepad.id-1], horizontal_axis_id-1);
    if(value < dead_zone)
        return 0.0f;
    return (value-dead_zone)/(JOY_AXIS_MAX-dead_zone);
}




Gamepad::Button::Button(Gamepad& gamepad, unsigned int id)
    : gamepad(gamepad), id(id)
{}

bool Gamepad::Button::pressed() const
{
    if(gamepad.id == 0 || gamepad.id-1 >= numjoysticks)
        return false;

    SDL_Event& event = ((InputData*)gamepad.input.data)->event;
    if(event.type == SDL_JOYBUTTONDOWN)
        return (event.jbutton.which == int(gamepad.id-1) && event.jbutton.button == id-1);
    return false;
}

bool Gamepad::Button::released() const
{
    if(gamepad.id == 0 || gamepad.id-1 >= numjoysticks)
        return false;

    SDL_Event& event = ((InputData*)gamepad.input.data)->event;
    if(event.type == SDL_JOYBUTTONUP)
        return (event.jbutton.which == int(gamepad.id-1) && event.jbutton.button == id-1);
    return false;
}

bool Gamepad::Button::held() const
{
    if(gamepad.id == 0 || gamepad.id-1 >= numjoysticks)
        return false;

    return SDL_JoystickGetButton(joysticks[gamepad.id-1], id-1);
}



}

